using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Newtonsoft.Json.Serialization;

namespace NExt.Serialization.JsonNet
{
    /// <summary>
    /// DefaultContractResolver do not return overriden generic member.
    /// We override and change this behaviour to return derived property.
    /// </summary>
    public class ContractResolver : DefaultContractResolver
    {
        protected override List<MemberInfo> GetSerializableMembers(Type objectType)
        {
            List<MemberInfo> serializableMembers = GetFieldsAndProperties(objectType, BindingFlags.Public | BindingFlags.Instance);
            return serializableMembers;
        }

        private static List<MemberInfo> GetFieldsAndProperties(Type type, BindingFlags bindingAttr)
        {
            IEnumerable<MemberInfo> fieldInfos = GetFields(type, bindingAttr);
            IEnumerable<MemberInfo> propertyInfos = GetProperties(type, bindingAttr);

            List<MemberInfo> targetMembers = new List<MemberInfo>();
            targetMembers.AddRange(fieldInfos);
            targetMembers.AddRange(propertyInfos);

            List<MemberInfo> distinctMembers = new List<MemberInfo>(targetMembers.Count);
            var groupedMembers = targetMembers.GroupBy(m => m.Name).Select(g => new { Count = g.Count(), Members = g });
            foreach (var groupedMember in groupedMembers)
            {
                if (groupedMember.Count == 1)
                {
                    distinctMembers.Add(groupedMember.Members.First());
                }
                else
                {
                    var members = groupedMember.Members.Where(m => IsOverridenGenericMember(m, bindingAttr) || m.Name == "Item");
                    distinctMembers.AddRange(members);
                }
            }

            return distinctMembers;
        }

        private static IEnumerable<MemberInfo> GetFields(Type targetType, BindingFlags bindingAttr)
        {
            List<MemberInfo> fieldInfos = new List<MemberInfo>(targetType.GetFields(bindingAttr));
            return fieldInfos;
        }

        private static IEnumerable<MemberInfo> GetProperties(Type targetType, BindingFlags bindingAttr)
        {
            List<MemberInfo> propertyInfos = new List<MemberInfo>(targetType.GetProperties(bindingAttr));
            return propertyInfos;
        }

        private static bool IsOverridenGenericMember(MemberInfo memberInfo, BindingFlags bindingAttr)
        {
            if (memberInfo.MemberType != MemberTypes.Field && memberInfo.MemberType != MemberTypes.Property)
                throw new ArgumentException("Member must be a field or property.");

            Type declaringType = memberInfo.DeclaringType;
            if (!declaringType.IsGenericType)
                return false;
            Type genericTypeDefinition = declaringType.GetGenericTypeDefinition();
            if (genericTypeDefinition == null)
                return false;
            MemberInfo[] members = genericTypeDefinition.GetMember(memberInfo.Name, bindingAttr);
            if (members.Length == 0)
                return false;
            Type memberUnderlyingType = GetMemberUnderlyingType(members[0]);
            if (!memberUnderlyingType.IsGenericParameter)
                return false;

            return true;
        }

        private static Type GetMemberUnderlyingType(MemberInfo member)
        {
            switch (member.MemberType)
            {
                case MemberTypes.Field:
                    return ((FieldInfo)member).FieldType;
                case MemberTypes.Property:
                    return ((PropertyInfo)member).PropertyType;
                case MemberTypes.Event:
                    return ((EventInfo)member).EventHandlerType;
                default:
                    throw new ArgumentException("MemberInfo must be of type FieldInfo, PropertyInfo or EventInfo", "member");
            }
        }
    }
}